/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <arch/scr.h>
#include <arch/exception.h>
#include <stdlib/ks_string.h>
#include <tools/rbtree.h>
#include <core/syscall.h>
#include <core/fault.h>
#include <core/mm.h>
#include <core/schedule.h>
#include <core/task.h>
#include <caps/cnode.h>
#include <caps/capability.h>
#include <caps/task.h>
#include <uapi/errors.h>
#include <uapi/fault.h>
#include <uapi/syscall.h>

#define SYSCALL_DEFINE(name)                                                                                  \
    void sys_##name(unsigned long sn, unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, \
                    unsigned long a4, unsigned long a5, unsigned long a6)

typedef void (*syscall_t)();

cptr_t eptable[EP_COUNT];
#define EP_NAME_MAX   20
#define EP_NAME_COUNT 50
cptr_t ep_table_n[EP_NAME_COUNT];
char   ep_nametable[EP_NAME_COUNT][EP_NAME_MAX];

#define DCACHE_INVAL           0
#define DCACHE_CLEAN_INVAL     1
#define DCACHE_CLEAN_INVAL_ALL 2

SYSCALL_DEFINE(cache_op)
{
    unsigned long start = a0;
    unsigned long end   = a1;

    flush_dcache_range(start, end);
    task_set_retcode(current(), ESUCCESS);
}

SYSCALL_DEFINE(cpu_usage)
{
    extern unsigned int cpu_usage;
    task_set_retcode(current(), cpu_usage);
}

SYSCALL_DEFINE(get_cptr)
{
    task_set_retcode(current(), current()->cptr);
}

SYSCALL_DEFINE(rbtree_left_most)
{
    rbnode_t *tree = (rbnode_t *)a0;
    tree           = rbtree_left_most((rbnode_t *)a0);
    task_set_retcode(current(), (unsigned long)tree);
}

SYSCALL_DEFINE(rbnode_next)
{
    rbnode_t *tree      = (rbnode_t *)a0;
    rbnode_t *cur       = (rbnode_t *)a1;
    rbnode_t *tree_next = rbnode_next(tree, cur);
    task_set_retcode(current(), (unsigned long)tree_next);
}

SYSCALL_DEFINE(rbnode_getdata)
{
    rbnode_t *tree = (rbnode_t *)a0;
    task_set_retcode(current(), (unsigned long)tree->data);
}

SYSCALL_DEFINE(current)
{
    tcb_t        *crtt = current();
    tcb_t        *p_thread;
    capability_t *p_cap;
    cptr_t       *cur = (cptr_t *)a0;
    char         *sur = (char *)a1;
    if (a2 == 0UL) {
        if (a0) {
            *cur = crtt->cptr;
        }
        if (a1) {
            (void)ks_strcpy(sur, crtt->name);
            *(sur + ks_strlen(crtt->name)) = 0;
        }
    } else if (a2 && crtt->elf_cptr) {
        if (a0) {
            *cur = crtt->elf_cptr;
        }
        if (a1) {
            p_cap    = cnode_cptr_lookup(task_get_cnode(crtt), crtt->elf_cptr, sizeof(unsigned long) * 8U);
            p_thread = cap_task_get_obj(p_cap);
            (void)ks_strcpy(sur, p_thread->name);
            *(&sur[ks_strlen(p_thread->name)]) = '\0';
        }
    }

    task_set_retcode(current(), ESUCCESS);
}

SYSCALL_DEFINE(reg_get_epname)
{
    char *name = (char *)a0;
    assert(ks_strlen(name) <= 20);
    cptr_t  ep  = (cptr_t)a1;
    cptr_t *pep = (cptr_t *)a2;

    int i;
    if (!pep) {
        for (i = 0; i < EP_NAME_COUNT; i++) {
            if (!ep_table_n[i]) {
                break;
            }
        }
        if (i == EP_NAME_COUNT) {
            task_set_retcode(current(), ERR_TO_RETCODE(-1));
            return;
        }
        (void)ks_strcpy(ep_nametable[i], name);
        ep_table_n[i] = ep;
    } else {
        for (i = 0; i < EP_NAME_COUNT; i++) {
            if (!ks_strcmp(name, ep_nametable[i])) {
                break;
            }
        }
        if (i == EP_NAME_COUNT) {
            task_set_retcode(current(), ERR_TO_RETCODE(-1));
            return;
        }
        *pep = ep_table_n[i];
    }
    task_set_retcode(current(), ESUCCESS);
}

SYSCALL_DEFINE(reg_get_ep)
{
    int     index = (int)a0;
    cptr_t  ep    = (cptr_t)a1;
    cptr_t *pep   = (cptr_t *)a2;
    assert(index < EP_COUNT);
    if (!pep) {
        eptable[index] = ep;
    } else {
        *pep = eptable[index];
    }
    task_set_retcode(current(), ESUCCESS);
}

SYSCALL_DEFINE(get_core_id)
{
    word_t mpidr = scr_read_MPIDR();
    mpidr        = (mpidr & 0xff00UL) >> 8;
    task_set_retcode(current(), mpidr);
}

SYSCALL_DEFINE(thread_id)
{
    tcb_t *task = current();
    task_set_retcode(current(), task->id);
}
SYSCALL_DEFINE(boottime)
{
    task_set_retcode(current(), (unsigned long)timer_current().time - 0xFFA00000LLU);
}

SYSCALL_DEFINE(irq_option)
{
    if (a1 == 1) {
        exception_enable_irq(a0);
        task_set_retcode(current(), ESUCCESS);
    } else if (!a1) {
        exception_disable_irq(a0);
        task_set_retcode(current(), ESUCCESS);

    } else {
        task_set_retcode(current(), EINVALID);
    }
}

syscall_t systable[SYS_COUNT] = {
    [SYS_cache_op]         = sys_cache_op,
    [SYS_cpu_usage]        = sys_cpu_usage,
    [SYS_get_core_id]      = sys_get_core_id,
    [SYS_get_cptr]         = sys_get_cptr,
    [SYS_boottime]         = sys_boottime,
    [SYS_thread_id]        = sys_thread_id,
    [SYS_reg_get_ep]       = sys_reg_get_ep,
    [SYS_rbtree_left_most] = sys_rbtree_left_most,
    [SYS_rbnode_next]      = sys_rbnode_next,
    [SYS_rbnode_getdata]   = sys_rbnode_getdata,
    [SYS_reg_get_epname]   = sys_reg_get_epname,
    [SYS_current]          = sys_current,
    [SYS_irq_option]       = sys_irq_option,
};

void syscall_handler(context_t *context)
{
    if (context->regs[0] < SYS_COUNT) {
        systable[context->regs[0]](context->regs[0], context->regs[1], context->regs[2], context->regs[3],
                                   context->regs[4], context->regs[5], context->regs[6], context->regs[7]);
    } else {
        cap_dispatcher(context->regs[0], context->regs[1], context->regs[2], context->regs[3], context->regs[4],
                       context->regs[5], context->regs[6], context->regs[7]);
    }

    schedule();
    context_restore_current();
}
